#ifndef CRUDE_OBSERVED_PTR_H_
#define CRUDE_OBSERVED_PTR_H_

#include <atomic>
#include <type_traits>
#ifndef _STD
#define _STD std::
#endif

namespace crude {

class _PRIVATE_observer_container {
  void *subject_;
  _STD atomic<size_t> observer_count_;
  ~_PRIVATE_observer_container() {}

public:
  _PRIVATE_observer_container(void *subject) noexcept
      : subject_(subject), observer_count_(0) {}
  void _Incref_observer() { observer_count_++; }
  size_t _Get_observer_count() const { return observer_count_; }

  void _Decref_observer() noexcept {
    if (observer_count_ > 0)
      observer_count_--;
    if (!subject_ && !observer_count_)
      delete this;
  }

  template <class _Ty> void _Destory_subject() noexcept {
    delete static_cast<_Ty *>(subject_);
    subject_ = nullptr;
    if (observer_count_ == 0)
      delete this;
  }

  bool _IsValid() const noexcept { return !!subject_; }

  void *_Get() const noexcept { return subject_; }

  void *_Release() {
    auto temp = subject_;
    subject_ = nullptr;
    return temp;
  }
};

template <class _Ty, _STD enable_if_t<!_STD is_array_v<_Ty>, int> = 0>
class _PRIVATE_observer_base {
protected:
  ~_PRIVATE_observer_base() = default;

  void _Copy_from(_PRIVATE_observer_container *rhs) {
    if (container_ == rhs)
      return;
    if (container_)
      container_->_Decref_observer();
    container_ = rhs;
    if (container_)
      container_->_Incref_observer();
  }

  void _Move_from_observed_ptr(_PRIVATE_observer_container *&&rhs) {
    if (container_) {
      container_->_Decref_observer();
      container_ = nullptr;
    }
    _STD swap(container_, rhs);
  }

  constexpr _PRIVATE_observer_base(
      _PRIVATE_observer_container *container = nullptr)
      : container_(container) {}
  _PRIVATE_observer_container *container_;

public:
  _PRIVATE_observer_base(const _PRIVATE_observer_base &rhs) = delete;

  _PRIVATE_observer_base &operator=(const _PRIVATE_observer_base &rhs) = delete;

  _PRIVATE_observer_base(_PRIVATE_observer_base &&rhs) = delete;

  _PRIVATE_observer_base &operator=(_PRIVATE_observer_base &&rhs) = delete;

  operator bool() const { return container_ ? container_->_IsValid() : false; }

  _Ty *get() const {
    return container_ ? static_cast<_Ty *>(container_->_Get()) : nullptr;
  }

  _Ty &operator*() const { return *(static_cast<_Ty *>(container_->_Get())); }

  _Ty *operator->() const {
    return container_ ? static_cast<_Ty *>(container_->_Get()) : nullptr;
  }

  size_t get_observer_count() const {
    return container_ ? container_->_Get_observer_count() : 0;
  }

  bool operator==(const _PRIVATE_observer_base &that) const {
    return container_ == that.container_;
  }

  bool operator!=(const _PRIVATE_observer_base &that) const {
    return !(*this == that);
  }

  bool operator==(const _Ty *that) const {
    return (container_ ? container_->_Get() : nullptr) == that;
  }

  bool operator!=(const _Ty *that) const { return !(*this == that); }

  friend bool operator==(const _Ty *lhs, const _PRIVATE_observer_base &rhs) {
    return rhs == lhs;
  }

  friend bool operator!=(const _Ty *lhs, const _PRIVATE_observer_base &rhs) {
    return rhs != lhs;
  }

  bool operator<(const _PRIVATE_observer_base &that) const {
    return container_ < that.container_;
  }

  _PRIVATE_observer_container *_GetContainer() const { return container_; }
};

template <class _Ty, _STD enable_if_t<!_STD is_array_v<_Ty>, int> = 0>
class subject_ptr : public _PRIVATE_observer_base<_Ty> {
public:
  subject_ptr(const subject_ptr &) = delete;
  subject_ptr &operator=(const subject_ptr &) = delete;

  // template <class _Other,
  //          _STD enable_if_t<_STD is_convertible<_Other, _Ty>, int> = 0>
  subject_ptr(subject_ptr /*<_Other>*/ &&rhs) {
    _STD swap(this->container_, rhs.container_);
  }

  // template <class _Other,
  //          _STD enable_if_t<_STD is_convertible<_Other, _Ty>, int> = 0>
  subject_ptr &operator=(subject_ptr /*<_Other>*/ &&rhs) {
    if (this->container_)
      this->container_->_Destory_subject<_Ty>();
    this->container_ = nullptr;
    _STD swap(this->container_, rhs.container_);
    return *this;
  }

  explicit subject_ptr(_Ty *subject = nullptr) {
    if (subject) {
      this->container_ = new _PRIVATE_observer_container(subject);
      _Enable_observed_from_this(*this, subject);
    }
  }

  _Ty *release() {
    _PRIVATE_observer_container *tmp = nullptr;
    _STD swap(tmp, this->container_);
    if (tmp) {
      return static_cast<_Ty *>(tmp->_Release());
    }
    return nullptr;
  }

  void reset(_Ty *subject = nullptr) {
    if (this->container_) {
      this->container_->_Destory_subject<_Ty>();
      this->container_ = nullptr;
    }
    if (subject) {
      this->container_ = new _PRIVATE_observer_container(subject);
      _Enable_observed_from_this(*this, subject);
    }
  }

  ~subject_ptr() {
    if (this->container_)
      this->container_->_Destory_subject<_Ty>();
  }

  void swap(subject_ptr &rhs) { _STD swap(this->container_, rhs.container_); }

  void swap(subject_ptr &&rhs) { _STD swap(this->container_, rhs.container_); }
};

template <class _Yty, class = void>
struct _Can_enable_observed : _STD false_type {};

template <class _Yty>
struct _Can_enable_observed<_Yty, _STD void_t<typename _Yty::_Eoft_type>>
    : _STD is_convertible<_STD remove_cv_t<_Yty> *,
                          typename _Yty::_Eoft_type *> {};

template <class _Ty, _STD enable_if_t<!_STD is_array_v<_Ty>, int> = 0>
class observed_ptr : public _PRIVATE_observer_base<_Ty> {
public:
  constexpr observed_ptr() noexcept {}

  observed_ptr(const subject_ptr<_Ty> &subject)
      : _PRIVATE_observer_base<_Ty>(subject._GetContainer()) {
    if (this->container_)
      this->container_->_Incref_observer();
  }

  observed_ptr(const observed_ptr &rhs) : _PRIVATE_observer_base<_Ty>(nullptr) {
    this->_Copy_from(rhs.container_);
  }

  observed_ptr &operator=(const observed_ptr &rhs) {
    if (this != &rhs)
      this->_Copy_from(rhs.container_);
    return *this;
  }

  observed_ptr(observed_ptr &&rhs) : _PRIVATE_observer_base<_Ty>(nullptr) {
    this->_Move_from_observed_ptr(_STD move(rhs.container_));
  }

  observed_ptr &operator=(observed_ptr &&rhs) {
    if (this != &rhs)
      this->_Move_from_observed_ptr(_STD move(rhs.container_));
    return *this;
  }

  ~observed_ptr() {
    if (this->container_)
      this->container_->_Decref_observer();
  }

  void reset() {
    if (this->container_)
      this->container_->_Decref_observer();
    this->container_ = nullptr;
  }

  template <class Other, std::enable_if_t<std::is_same_v<_Ty, Other> &&
                                              _Can_enable_observed<Other>,
                                          int> = 0>
  void reset(_Ty *_Ptr) {
    if (this->container_)
      this->container_->_Decref_observer();
    this->container_ = nullptr;
    if (_Ptr)
      *this = _Ptr->observe_from_this();
  }
};

template <class _Other, class _Yty>
void _Enable_observed_from_this1(const subject_ptr<_Other> &_This, _Yty *_Ptr,
                                 _STD true_type) {
  if (_Ptr && !(bool)(_Ptr->_Wptr)) {
    _Ptr->_Wptr = _This;
  }
}

template <class _Other, class _Yty>
void _Enable_observed_from_this1(const subject_ptr<_Other> &, _Yty *,
                                 _STD false_type) {}

template <class _Other, class _Yty>
void _Enable_observed_from_this(const subject_ptr<_Other> &_This, _Yty *_Ptr) {
  _Enable_observed_from_this1(
      _This, _Ptr,
      _STD conjunction<_STD negation<_STD is_array<_Other>>,
                       _STD negation<_STD is_volatile<_Yty>>::type,
                       _Can_enable_observed<_Yty>>{});
}

template <class _Ty> class enable_observed_from_this {
public:
  using _Eoft_type = enable_observed_from_this;
  observed_ptr<_Ty> observe_from_this() const noexcept { return (_Wptr); }

protected:
  constexpr enable_observed_from_this() noexcept : _Wptr() {}

  enable_observed_from_this(const enable_observed_from_this &) noexcept
      : _Wptr() {}

  enable_observed_from_this &
  operator=(const enable_observed_from_this &) noexcept {
    return (*this);
  }

  ~enable_observed_from_this() = default;

private:
  mutable observed_ptr<_Ty> _Wptr;
  template <class _Other, class _Yty>
  friend void _Enable_observed_from_this1(const subject_ptr<_Other> &_This,
                                          _Yty *_Ptr, _STD true_type);
};

// function template make_subject
template <class _Ty, class... _Types,
          _STD enable_if_t<!_STD is_array_v<_Ty>, int> = 0>
inline subject_ptr<_Ty> make_subject(_Types &&... _Args) { // make a subject_ptr
  return (subject_ptr<_Ty>(new _Ty(_STD forward<_Types>(_Args)...)));
}

template <class _Ty, class... _Types,
          _STD enable_if_t<_STD extent_v<_Ty> != 0, int> = 0>
void make_subject(_Types &&...) = delete;

} // namespace crude
#endif // CRUDE_OBSERVED_PTR_H_
